#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Diagnostics;
using System.Threading;
using C5;

namespace Grawlix.Concurrency
{
	/// <summary>
	/// A series of futures that can be accessed as they complete.
	/// </summary>
	/// <typeparam name="T">The result type.</typeparam>
	public class FutureChannel<T>
	{
		/// <summary>
		/// The executor used to run the actions.
		/// </summary>
		private readonly IExecutor _executor;

		/// <summary>
		/// The synchronization monitor.
		/// </summary>
		private readonly object _sync = new object();

		/// <summary>
		/// The list of completed results.
		/// </summary>
		private LinkedList<Future<T>> _completionList =
			new LinkedList<Future<T>>();

		/// <summary>
		/// The number of futures currently executing.
		/// </summary>
		private int _count;

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="FutureChannel{T}"/>class with a 
		/// <see cref="ThreadPoolExecutor"/>.
		/// </summary>
		public FutureChannel()
			: this(new ThreadPoolExecutor())
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="FutureChannel{T}"/>
		/// class.
		/// </summary>
		/// <param name="executor">The executor to run on.</param>
		/// <exception cref="ArgumentNullException"><c>executor</c> is
		/// null.</exception>
		public FutureChannel(IExecutor executor)
		{
			if (executor == null)
				throw new ArgumentNullException("executor");
			_executor = executor;
		}

		/// <summary>
		/// Notifies that an action has begun.
		/// </summary>
		private void Announce()
		{
			lock (_sync)
			{
				_count++;
			}
		}

		/// <summary>
		/// Completes the action taken.
		/// </summary>
		/// <param name="future">The action that completed.</param>
		private void Complete(Future<T> future)
		{
			lock (_sync)
			{
				_completionList.Add(future);
				_count--;
				Debug.Assert(_count >= 0);
				if (_count == 0)
					Monitor.PulseAll(_sync);
			}
		}

		/// <summary>
		/// Dequeues all futures from the list and returns them.
		/// </summary>
		/// <returns>The possibly empty list of completed futures.
		/// </returns>
		public ICollection<Future<T>> DequeueAll()
		{
			lock (_sync)
			{
				LinkedList<Future<T>> result = _completionList;
				_completionList = new LinkedList<Future<T>>();
				return result;
			}
		}

		/// <summary>
		/// Adds the specified function to the pool.
		/// </summary>
		/// <param name="function">The function to run.</param>
		/// <exception cref="ArgumentNullException"><c>function</c> is
		/// null.</exception>
		public Future<T> Enqueue(Func<T> function)
		{
			if (function == null)
				throw new ArgumentNullException("function");
			Announce();
			var future = new DelayedFuture<T>();
			_executor.Execute(() =>
				{
					future.Execute(new DirectExecutor(), function);
					Complete(future);
				});
			return future;
		}

		/// <summary>
		/// Tries to dequeue a completed future from the channel.
		/// </summary>
		/// <returns>The completed future, or null if none is ready.
		/// </returns>
		public Future<T> TryDequeue()
		{
			lock (_sync)
			{
				if (_completionList.IsEmpty)
					return null;
				return _completionList.RemoveFirst();
			}
		}

		/// <summary>
		/// Waits until all actions are finished or an error occurs.
		/// </summary>
		/// <exception cref="System.Exception">If an error occurs in
		/// one of the actions.</exception>
		public void Wait()
		{
			Wait(Timeout.Infinite);
		}

		/// <summary>
		/// Waits until all actions are finished, an error occurs, or
		/// the timeout expires.
		/// </summary>
		/// <param name="millisecondTimeout">The maximum time to wait,
		/// in milliseconds.</param>
		/// <returns><c>true</c> if finished, <c>false</c> if the
		/// timeout expired.</returns>
		/// <exception cref="Exception">If an error occurs in one of
		/// the actions.</exception>
		public bool Wait(int millisecondTimeout)
		{
			lock (_sync)
			{
				if (_count != 0)
				{
					if (!Monitor.Wait(_sync, millisecondTimeout))
						return false;
				}
				return true;
			}
		}

		/// <summary>
		/// Waits until all actions are finished, an error occurs, or
		/// the timeout expires.
		/// </summary>
		/// <param name="timeout">The maximum time to wait.</param>
		/// <returns><c>true</c> if finished, <c>false</c> if the
		/// timeout expired.</returns>
		/// <exception cref="Exception">If an error occurs in one of
		/// the actions.</exception>
		public void Wait(TimeSpan timeout)
		{
			Wait(timeout.Milliseconds);
		}
	}
}